/*
 * =====================================================================================
 *
 *       Filename:  main.cpp
 *
 *    Description:
 *
 *        Version:  1.0
 *        Created:  2022年07月28日 11时19分33秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  vyouzhi (vz), vyouzhi@gmail.com
 *   Organization:  Etomc2.com
 *        LICENSE:  BSD-3-Clause license
 *
 *  Copyright (c) 2019-2022, vyouzhi
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  * Neither the name of vyouzhi and/or the DataFrame nor the
 *  names of its contributors may be used to endorse or promote products
 *  derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL vyouzhi BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * =====================================================================================
 */

#include <stdlib.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>

#include "E2Q.hpp"
#include "Toolkit/DaemonProcess.hpp"
#include "Toolkit/GlobalConfig.hpp"
#include "Toolkit/Norm.hpp"
#include "Toolkit/Util.hpp"
#include "Toolkit/eLog.hpp"
#include "ast/ParserCtx.hpp"
using namespace e2q;
/*
 * ===  FUNCTION  =============================
 *
 *         Name:  only_run
 *  ->  void *
 *  Parameters:
 *  - size_t  arg
 *  Description:
 *
 * ============================================
 */
void only_run(const char* f)
{
    e2::ParserCtx ctx;

    int ret = ctx.toparse(f);

    if (ret == -1) {
        return;
    }

    std::int64_t a = 19;
    CodeGenContext context;

    createCoreFunctions(context);

    Block* block = ctx.RootBlock();
    bool isgc = context.generateCode(block);

    if (isgc) {
        context.runCode();
        int ret = context.runFunction(a, a);
        elog::echo("ret:", ret);
    }
    else {
        elog::bug("generateCode is error");
    }

} /* -----  end of function only_run  ----- */

/*
 * ===  FUNCTION  =============================
 *
 *         Name:  check_log_dir
 *  ->  void *
 *  Parameters:
 *  - size_t  arg
 *  Description:
 *
 * ============================================
 */
void check_log_dir()
{
    struct stat info;
    const char* optarg = e2q::GlobalMainArguments.log_dir.c_str();
    if (stat(optarg, &info) != 0) {
        mkdir(optarg, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    }
    else if (info.st_mode & S_IFDIR) {
    }
    else {
        mkdir(optarg, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    }
} /* -----  end of function check_log_dir  ----- */

/*
 * ===  FUNCTION  =============================
 *
 *         Name:  e2q_action
 *  ->  void *
 *  Parameters:
 *  - size_t  arg
 *  Description:
 *
 * ============================================
 */
int e2q_action(int argc, char* argv[])
{
    pid_t pid;
    int fd[2];
    char buffer[2];
    pipe(fd);

    char* b = nullptr;
    char* e = nullptr;
    char* s = nullptr;
    char* p = nullptr;
    char* f = nullptr;
    char* i = nullptr;

    std::string searh_path = "";
    int index;

    int run = 0;  // 第几次运行进程
    std::size_t account_number_idx =
        0;  // oms 默认 3 account 数量, ea 0 account idx

    std::string properties = "";
    std::vector<std::string> eas_el;
    std::string oms_el;
    rlimit rlim;
    int h;

    e2q::DProcess process;
    char pid_file[] = "/tmp/e2q_%ld.pid";
    std::string help =
        "%s -e script.e2 \n \
                Usage: \n \
                -h help \n \
                -a if oms default fix account number or if ea account index \n \
                -b bin history ticket file directory \n \
                -d daemon run \n \
                -e which loading ea e2l script \n \
                -f log directory \n \
                -l show llvm ir for e2l \n \
                -n read number bin history tickets \n \
                -i e2l import codes directory,def:/usr/local/include/e2/ \n \
                -s which loading oms e2l script \n \
                -o only test e2l script \n \
                -p which loading db properties \n \
                -r e2l run number \n \
                -v show e2q version \n \
                ";

    if (argc < 2) {
        printf(help.c_str(), argv[0]);

        exit(-1);
    }
    e2q::process_debug = false;

    //./e2q -e node.e2 -e node2.e2 -e node3.e2
    while ((h = getopt(argc, argv, "hdlve:s:p:r:a:o:f:i:b:n:")) != -1) {
        switch (h) {
            case 'h': {
                printf(help.c_str(), argv[0]);

                exit(-1);
            }
            case 'd':

                e2q::GlobalMainArguments.do_daemonize = 1;
                break;

            case 'l':
                e2q::llvm_ir = true;
                break;
            case 'v': {
                elog::echo(version::version_full);
                exit(0);
            }
            case 'e': {
                e = optarg;
                if (e != nullptr) {
                    eas_el.push_back(std::string(e));
                    e2q::GlobalMainArguments.ea_or_oms = false;
                }
                break;
            }
            case 's': {
                s = optarg;
                if (s != nullptr) {
                    oms_el = std::string(s);
                    e2q::GlobalMainArguments.ea_or_oms = true;
                }
                break;
            }
            case 'p': {
                p = optarg;
                if (p != nullptr) {
                    int file_ok = access(p, R_OK);
                    if (file_ok != -1) {
                        properties = std::string(p);
                    }
                    else {
                        printf("properties is not exist!\n");
                    }
                }
                break;
            }
            case 'r': {
                if (optarg != nullptr) {
                    run = atoi(optarg);
                }
                break;
            }
            case 'a': {
                if (optarg != nullptr) {
                    account_number_idx = (std::size_t)atoi(optarg);
                    if (account_number_idx < 0) {
                        account_number_idx = 0;
                    }
                }
                break;
            }
            case 'o': {
                e = optarg;
                if (e != nullptr) {
                    only_run(e);
                }
                return 0;
            }
            case 'f': {
                f = optarg;
                if (f != nullptr) {
                    e2q::GlobalMainArguments.log_dir = std::string(f);
                }
                break;
            }
            case 'i': {
                i = optarg;
                if (i != nullptr) {
                    searh_path = std::string(i);
                }
                break;
            }
            case 'b': {
                b = optarg;
                if (b != nullptr) {
                    e2q::GlobalMainArguments.bin_dir = std::string(b);
                }
                break;
            }
            case 'n': {
                if (optarg != nullptr) {
                    e2q::GlobalMainArguments.number_for_bin_read = atoi(optarg);
                }
                break;
            }
            case '?':
            default:
                printf("%s -h\n", argv[0]);
                exit(-1);
        }
    }

    for (index = optind; index < argc; index++) {
        printf("Non-option argument %s\n", argv[index]);
        printf("%s -h\n", argv[0]);
        exit(-1);
    }

    if (e2q::GlobalMainArguments.do_daemonize == 1) {
        process.sig();
        process.init(argv[0]);

        process.save_pid(getpid(), pid_file);
    }

    if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
        elog::bug("failed to getrlimit number of files");
        exit(1);
    }
    else {
        rlim.rlim_cur = MAXCONNS;
        rlim.rlim_max = MAXCONNS;
        if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
            elog::bug(
                "failed to set rlimit for open files. Try starting as root "
                "or "
                "requesting smaller maxconns value.");
            exit(1);
        }
    }

    check_log_dir();
#ifndef DEBUG
    char log_file[256] = {0};
    if (e2q::GlobalMainArguments.ea_or_oms) {
        snprintf(log_file, 256, "%s/oms.log",
                 e2q::GlobalMainArguments.log_dir.c_str());
    }
    else {
        int number_log = 0;
        for (const auto& p :
             std::filesystem::directory_iterator(GlobalMainArguments.log_dir)) {
            if (p.is_directory()) {
                continue;
            }
            number_log++;
        }
        snprintf(log_file, 256, "%s/ea_%d.log",
                 e2q::GlobalMainArguments.log_dir.c_str(), number_log);
    }

    GlobalMainArguments.log_io.open(log_file, std::ios::app);
#endif

    const auto processor_count = std::thread::hardware_concurrency();

    e2q::UtilTime ut;
    // 128 threads number
    // e mathematical constant deci start
    std::size_t now = ut.time() - 718281828 + 128 * (1 + run);
    std::size_t proce = eas_el.size();
    e2q::process_run_number = run;
    std::vector<pid_t> pids;
    std::size_t m = 0;
    e2q::E2Q _e2q;
    if (e2q::GlobalMainArguments.log_dir.length() > 0) {
        _e2q.log_dir(e2q::GlobalMainArguments.log_dir);
    }
    if (searh_path.length() > 0) {
        _e2q.search_dir(searh_path);
    }

    if (proce == 1 && oms_el.size() == 0) {
        // 单独运行 fix account node
        printf("[Child] I'm Child process\n");
        printf("[Child] Child's PID is %d name: %s \n", getpid(),
               eas_el[m].c_str());

        _e2q.setCfg(eas_el[m], properties);

        _e2q.trader(account_number_idx, now, run);

        if (e2q::GlobalMainArguments.do_daemonize == 1) {
            process.rm_pid(pid_file);
        }
        return 0;
    }

    for (m = 0; m < proce; m++) {
        pid = fork();
        pids.push_back(pid);

        switch (pids[m]) {
            // PID == -1 代表 fork 出錯
            case -1:
                perror("fork()");
                exit(-1);

            // PID == 0 代表是子程序
            case 0: {
                printf("[Child] I'm Child process\n");
                printf("[Child] Child's PID is %d name: %s \n", getpid(),
                       eas_el[m].c_str());
                _e2q.setCfg(eas_el[m], properties);

                if (oms_el.size() != 0) {
                    close(fd[1]);
                    read(fd[0], buffer, 2);
                    close(fd[0]);
                }

                _e2q.trader(m, now, run);

                return 0;
            }
            // PID > 0 代表是父程序
            default:
                // printf("{parent} Parent PID is %d\n", getpid());
                break;
        }

        now += processor_count;
    }

    if (oms_el.size() > 0) {
        _e2q.setCfg(oms_el, properties);

        e2q::func_type<> call_child_fun = nullptr;

        if (proce > 0) {
            call_child_fun = [fd]() {
                int num = 1;

                close(fd[0]);
                write(fd[1], &num, sizeof(num));
                close(fd[1]);
            };  // -----  end lambda  -----
        }

        if (proce > account_number_idx) {
            account_number_idx = proce;
        }
        if (account_number_idx <= 0) {
            account_number_idx = 3;
        }
        _e2q.exchange(account_number_idx, call_child_fun);
    }

    int exit_status;

    for (m = 0; m < proce; m++) {
        pid = wait(&exit_status);
        elog::echo("child pid ", pid, " status 0x%x", (long)pid, exit_status);
    }
    if (e2q::GlobalMainArguments.do_daemonize == 1) {
        process.rm_pid(pid_file);
    }
    return 0;

} /* -----  end of function e2q_action  ----- */
/*
 * ===  FUNCTION  =============================
 *         Name:  main
 *  Description:
 * ============================================
 */
int main(int argc, char* argv[])
{
    e2q_action(argc, argv);
    return EXIT_SUCCESS;
} /* ----------  end of function main  ---------- */
